//=============================================================================
//
// Demostrates lighting phong model. (diffuse, ambient, specular lights)
// (parallel, point , spot lights)
//
// Controls:
// - A, D, W, S: Move Camera
// - X, Z: Zoom in, zoom out
// - N, M: Decrement, increment spot light cone angle.
// - 1, 2, 3: Change light to: parallel, point or spot.
//
//=============================================================================

#include "LightingApp.h"

namespace 
{
	const DWORD kGridRows = 129;
	const DWORD kGridColumns = 129;
	const DWORD kWaveRows = 261;
	const DWORD kWaveColumns = 261;
}

namespace D3D10
{
	LightingApp::LightingApp(const HINSTANCE hInstance)
		: D3DApp(hInstance)
		, mGrid(kGridRows, kGridColumns)
		, mWaves(kWaveRows, kWaveColumns)
		, mEffect(0)
		, mTechnique(0)
		, mVertexLayout(0)
		, mEffectWVPVariable(0)
		, mEffectWorldVar(0)
		, mWireframeRS(0)
		, mEffectEyePosVar(0)
		, mEffectLightVar(0)
		, mEffectLightType(0)
		, mEyePos(0.0f, 0.0f, 0.0f)
		, mClearColor(0.9f, 0.9f, 0.9f, 1.0f)
		, mLightType(0)
		, mRadius(100.0f)
		, mTheta(0.0f)
		, mPhi(D3D10Utils::PI * 0.4f)
	{
		D3DXMatrixIdentity(&mLandWorld);
		D3DXMatrixIdentity(&mWavesWorld);
		D3DXMatrixIdentity(&mView);
		D3DXMatrixIdentity(&mProjection);
		D3DXMatrixIdentity(&mWVP); 
	}
	
	LightingApp::~LightingApp()
	{
		if (mDevice)
			mDevice->ClearState();
	
		D3D10Utils::ReleaseCOM(mEffect);
		D3D10Utils::ReleaseCOM(mVertexLayout);
		D3D10Utils::ReleaseCOM(mWireframeRS);
	}
	
	void LightingApp::initApp()
	{
		D3DApp::initApp();

		mGrid.init(mDevice, 1.0f, 1.0f);
		mWaves.init(mDevice, 0.5f, 0.03f, 4.0f, 0.4f);

		// buildRenderStates();
		buildFX();
		buildVertexLayouts();

		mLightType = 0;

		// Parallel light.
		mLights[0].mDir = D3DXVECTOR3(0.57735f, -0.57735f, 0.57735f);
		mLights[0].mAmbient = D3DXCOLOR(0.2f, 0.2f, 0.2f, 1.0f);
		mLights[0].mDiffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); // Black light
		mLights[0].mSpecular = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);

		// Point light position is changed every frame to animate.
		mLights[1].mAmbient = D3DXCOLOR(0.4f, 0.4f, 0.4f, 1.0f);
		mLights[1].mDiffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
		mLights[1].mSpecular = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
		mLights[1].mAtt.x = 0.0f; // a0 coef
		mLights[1].mAtt.y = 0.1f; // a1 coef
		mLights[1].mAtt.z = 0.0f; // a2 coef
		mLights[1].mRange = 50.0f;

		// Spotlight position and direction changed every frame to animate.
		mLights[2].mAmbient = D3DXCOLOR(0.4f, 0.4f, 0.4f, 1.0f);
		mLights[2].mDiffuse - D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); // Black light
		mLights[2].mSpecular = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); // Black light
		mLights[2].mAtt.x = 1.0f; // a0 coef
		mLights[2].mAtt.y = 0.0f; // a1 coef
		mLights[2].mAtt.z = 0.0f; // a2 coef
		mLights[2].mSpotLightPower = 64.0f;
		mLights[2].mRange = 10000.0f;
	}
	
	void LightingApp::onResize()
	{
		D3DApp::onResize();
	
		const float aspect = static_cast<float> (mClientWidth / mClientHeight);
		D3DXMatrixPerspectiveFovLH(&mProjection, D3D10Utils::PI * 0.25f, aspect, 1.0f, 1000.0f);
	}
	
	void LightingApp::updateScene(const float deltaTime)
	{
		D3DApp::updateScene(deltaTime);
	
		// Every quarter second, generate a random wave.
		static float t_base = 0.0f;
		if( (mTimer.getGameTime() - t_base) >= 0.25f )
		{
			t_base += 0.25f;

			const DWORD i = 1 + rand() % (kWaveRows - 3);
			const DWORD j = 1 + rand() % (kWaveColumns - 3);
			const float r = D3D10Utils::RandF(1.0f, 2.0f);

			mWaves.disturb(i, j, r);
		}

		// Update angles based on input to orbit camera around scene.
		if (GetAsyncKeyState('A') & 0x8000)	
			mTheta -= 1.0f * deltaTime;
		if (GetAsyncKeyState('D') & 0x8000)	
			mTheta += 1.0f * deltaTime;
		if (GetAsyncKeyState('W') & 0x8000)	
			mPhi -= 2.0f * deltaTime;
		if (GetAsyncKeyState('S') & 0x8000)	
			mPhi += 2.0f * deltaTime;
		if (GetAsyncKeyState('Z') & 0x8000)	
			mRadius -= 25.0f * deltaTime;
		if (GetAsyncKeyState('X') & 0x8000)	
			mRadius += 25.0f * deltaTime;

		// Restrict the angle mPhi and radius mRadius.
		if (mPhi < 0.1f)	
			mPhi = 0.1f;
		if (mPhi > D3D10Utils::PI - 0.1f)	
			mPhi = D3D10Utils::PI - 0.1f;

		if (mRadius < 25.0f) 
			mRadius = 25.0f;

		// Convert Spherical to Cartesian coordinates: mPhi measured from +y
		// and mTheta measured counterclockwise from -z.
		mEyePos.x =  mRadius * sinf(mPhi) * sinf(mTheta);
		mEyePos.z = -mRadius * sinf(mPhi) * cosf(mTheta);
		mEyePos.y =  mRadius * cosf(mPhi);

		// Build the view matrix.
		D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
		D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
		D3DXMatrixLookAtLH(&mView, &mEyePos, &target, &up);

		// Set light type based on user input.
		if (GetAsyncKeyState('1') & 0x8000)
			mLightType = 0;
		if (GetAsyncKeyState('2') & 0x8000)
			mLightType = 1;
		if (GetAsyncKeyState('3') & 0x8000)
			mLightType = 2;
		
		// Update spot light cone angle
		if (GetAsyncKeyState('N') & 0x8000)
			mLights[2].mSpotLightPower -= 2.0f;
		if (GetAsyncKeyState('M') & 0x8000)
			mLights[2].mSpotLightPower += 2.0f;

		// The point light circles the scene as a function of time,
		// staying 7 units above the land's or water's surface.
		mLights[1].mPos.x = 50.0f * cosf(mTimer.getGameTime());
		mLights[1].mPos.z = 50.0f * sinf(mTimer.getGameTime());
		mLights[1].mPos.y = D3D10Utils::Max(D3D10Utils::getHeight(mLights[1].mPos.x, mLights[1].mPos.z), 0.0f) 
			+ 7.0f;
	
		// The spotlight takes on the camera position and is aimed in the 
		// same direction the camera is looking. In this way, it looks 
		// like we are holding a flashlight.
		mLights[2].mPos = mEyePos;
		const D3DXVECTOR3 toTarget = target - mEyePos;
		D3DXVec3Normalize(&mLights[2].mDir, &toTarget);

		mWaves.update(deltaTime);
	}
	
	void LightingApp::drawScene()
	{
		D3DApp::drawScene();
	
		// Restore default states, input layout and primitive topology 
		// because mFont->DrawText changes them. 

		// Sets the depth-stencil state of the output-merger stage.
		mDevice->OMSetDepthStencilState(0, 0);
		
		// Set the blend state of the output-merger stage.
		const float blendFactors[] = {0.0f, 0.0f, 0.0f, 0.0f};
		mDevice->OMSetBlendState(0, blendFactors, 0xffffffff);

		// Bind an input-layout object to the input-assembler stage.
		mDevice->IASetInputLayout(mVertexLayout);

		// Bind information about the primitive type, 
		// and data order that describes input data for the input assembler stage.
		mDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

		// Set per frame constants.
		mEffectEyePosVar->SetRawValue(&mEyePos, 0, sizeof(D3DXVECTOR3));
		mEffectLightVar->SetRawValue(&mLights[mLightType], 0, sizeof(D3D10Utils::Light));
		mEffectLightType->SetInt(mLightType);
	
		D3D10_TECHNIQUE_DESC techDesc;
		mTechnique->GetDesc(&techDesc);

		for(UINT i = 0; i < techDesc.Passes; ++i)
		{
			ID3D10EffectPass* pass = mTechnique->GetPassByIndex(i);

			mWVP = mLandWorld * mView * mProjection;
			mEffectWVPVariable->SetMatrix(reinterpret_cast<float*> (&mWVP));
			mEffectWorldVar->SetMatrix(reinterpret_cast<float*> (&mLandWorld));
			pass->Apply(0);
			mGrid.draw();

			mWVP = mWavesWorld * mView * mProjection;
			mEffectWVPVariable->SetMatrix(reinterpret_cast<float*> (&mWVP));
			mEffectWorldVar->SetMatrix(reinterpret_cast<float*> (&mWavesWorld));
			pass->Apply(0);
			mWaves.draw();
		}
	
		// We specify DT_NOCLIP, so we do not care about width/height of the rect.
		RECT R = {5, 5, 0, 0};
		mDevice->RSSetState(0);
		mFont->DrawText(0, mFrameStats.c_str(), -1, &R, DT_NOCLIP, D3D10Utils::BLACK);
	
		mSwapChain->Present(0, 0);
	}

	void LightingApp::buildRenderStates()
	{
		// Describe the rasterizer state.
		D3D10_RASTERIZER_DESC rasterizerDesc;
		ZeroMemory(&rasterizerDesc, sizeof(D3D10_RASTERIZER_DESC));
		rasterizerDesc.FillMode = D3D10_FILL_WIREFRAME;
		rasterizerDesc.CullMode =  D3D10_CULL_BACK;
		
		D3D10Utils::ErrorHandler(mDevice->CreateRasterizerState(&rasterizerDesc, &mWireframeRS));
	}
	
	void LightingApp::buildFX()
	{
		DWORD shaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
		shaderFlags |= D3D10_SHADER_DEBUG;
		shaderFlags |= D3D10_SHADER_SKIP_OPTIMIZATION;
#endif

		ID3D10Blob* compilationErrors = 0;
		HRESULT hr = 0;
		hr = D3DX10CreateEffectFromFile(L"lighting.fx", 0, 0, 
			"fx_4_0", shaderFlags, 0, mDevice, 0, 0, &mEffect, &compilationErrors, 0);
		if(FAILED(hr))
		{
			if( compilationErrors )
			{
				MessageBoxA(0, (char*)compilationErrors->GetBufferPointer(), 0, 0);
				D3D10Utils::ReleaseCOM(compilationErrors);
			}
			DXTrace(__FILE__, (DWORD)__LINE__, hr, L"D3DX10CreateEffectFromFile", true);
		} 

		mTechnique = mEffect->GetTechniqueByName("LightTech");

		mEffectWVPVariable = mEffect->GetVariableByName("gWVP")->AsMatrix();
		mEffectWorldVar = mEffect->GetVariableByName("gWorld")->AsMatrix();
		mEffectEyePosVar = mEffect->GetVariableByName("gEyePosW");
		mEffectLightVar = mEffect->GetVariableByName("gLight");
		mEffectLightType = mEffect->GetVariableByName("gLightType")->AsScalar();
	}
	
	void LightingApp::buildVertexLayouts()
	{
		// Create the vertex input layout.
		D3D10_INPUT_ELEMENT_DESC vertexDesc[] =
		{
			{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D10_INPUT_PER_VERTEX_DATA, 0},
			{"NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0},
			{"DIFFUSE",  0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 24, D3D10_INPUT_PER_VERTEX_DATA, 0},
			{"SPECULAR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 40, D3D10_INPUT_PER_VERTEX_DATA, 0}
		};
	
		// Create an input-layout object to describe the 
		// input-buffer data for the input-assembler stage.
		D3D10_PASS_DESC PassDesc;
		 
		// Fill the pass description with the only pass that we have in
		// the current effect file.
		mTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);

		// PassDesc.pIAInputSignature is a pointer to the compiler shader that
		// contains a input signature which is validated against the array of elements (vertexDesc).
		// A shader signature is a list of the parameters that are either input to or 
		// output from a shader function. In Direct3D 10, adjacent stages effectively 
		// share a register array, where the output shader (or pipeline stage) writes 
		// data to specific locations in the register array and the input shader must 
		// read from the same locations. The API uses shader signatures to bind shader 
		// outputs with inputs without the overhead of semantic resolution. 
		// In Direct3D 10, input signatures are generated from a shader-input declaration 
		// and the output signature is generated from a shader-output declaration.
		D3D10Utils::ErrorHandler(mDevice->CreateInputLayout(vertexDesc, 4, PassDesc.pIAInputSignature,
			PassDesc.IAInputSignatureSize, &mVertexLayout));
	}
}
